@@ -1,11 +1,14 @@ |
||
1 | 1 |
module Agents |
2 | 2 |
class CommandAgent < Agent |
3 | 3 |
|
4 |
+ require 'open3' |
|
5 |
+ |
|
4 | 6 |
|
5 | 7 |
default_schedule "midnight" |
6 | 8 |
|
7 | 9 |
|
8 | 10 |
description <<-MD |
11 |
+ |
|
9 | 12 |
The CommandAgent can execute commands on your local system, returning the output. |
10 | 13 |
|
11 | 14 |
`command` specifies the command to be executed, and `path` will tell CommandAgent in what directory to run this command. |
@@ -14,7 +17,9 @@ module Agents |
||
14 | 17 |
|
15 | 18 |
CommandAgent can also act upon recieveing events. These events may contain their own path and command arguments. If they do not, CommandAgent will use the configured options. For this reason, please specify defaults even if you are planning to have this Agent respond to events. |
16 | 19 |
|
17 |
- The resulting event will contain the `command` which was executed, the `path` it was executed under, the `exit_status` of the command, and the actual `output`. CommandAgent will not log an error if the `exit_status` implies that something went wrong. |
|
20 |
+ The resulting event will contain the `command` which was executed, the `path` it was executed under, the `exit_status` of the command, the `errors`, and the actual `output`. CommandAgent will not log an error if the result implies that something went wrong. |
|
21 |
+ |
|
22 |
+ *Warning*: Misuse of this Agent can pose a security threat. |
|
18 | 23 |
|
19 | 24 |
MD |
20 | 25 |
|
@@ -25,7 +30,8 @@ module Agents |
||
25 | 30 |
'command' => 'pwd', |
26 | 31 |
'path' => '/home/Huginn', |
27 | 32 |
'exit_status' => '0', |
28 |
- 'output' => '/home' |
|
33 |
+ 'errors' => '', |
|
34 |
+ 'output' => '/home/Huginn' |
|
29 | 35 |
} |
30 | 36 |
MD |
31 | 37 |
|
@@ -55,18 +61,25 @@ module Agents |
||
55 | 61 |
path = opts['path'] || options['path'] |
56 | 62 |
|
57 | 63 |
result = nil |
58 |
- proc_stat = nil |
|
64 |
+ errors = nil |
|
65 |
+ exit_status = nil |
|
66 |
+ |
|
59 | 67 |
Dir.chdir(path){ |
60 |
- result = `#{command}` |
|
61 |
- proc_stat = $? |
|
68 |
+ begin |
|
69 |
+ stdin, stdout, stderr, wait_thr = Open3.popen3(command) |
|
70 |
+ exit_status = wait_thr.value.to_i |
|
71 |
+ result = stdout.gets(nil) |
|
72 |
+ errors = stderr.gets(nil) |
|
73 |
+ rescue Exception => e |
|
74 |
+ errors = e.to_s |
|
75 |
+ end |
|
62 | 76 |
} |
63 | 77 |
|
64 |
- exit_status = -404 # should never happen, but $? is global |
|
65 |
- exit_status = proc_stat.to_i if(proc_stat.is_a?(Process::Status)) |
|
66 |
- |
|
67 |
- result.chomp! if !result.nil? |
|
78 |
+ result.chomp! if result.is_a?(String) |
|
79 |
+ result = '' if result.nil? |
|
80 |
+ errors = '' if errors.nil? |
|
68 | 81 |
|
69 |
- vals = {"command" => command, "path" => path, "exit_status" => exit_status, "output" => result} |
|
82 |
+ vals = {"command" => command, "path" => path, "exit_status" => exit_status, "errors" => errors, "output" => result} |
|
70 | 83 |
evnt = create_event :payload => vals |
71 | 84 |
|
72 | 85 |
log("Ran '#{command}' under '#{path}'", :outbound_event => evnt) |
@@ -2,69 +2,69 @@ require 'spec_helper' |
||
2 | 2 |
|
3 | 3 |
describe Agents::CommandAgent do |
4 | 4 |
|
5 |
- before do |
|
6 |
- @valid_path = Dir.pwd |
|
7 |
- @valid_params = { |
|
8 |
- :path => @valid_path, |
|
9 |
- :command => "pwd", |
|
10 |
- :expected_update_period_in_days => "1", |
|
11 |
- } |
|
5 |
+ before do |
|
6 |
+ @valid_path = Dir.pwd |
|
7 |
+ @valid_params = { |
|
8 |
+ :path => @valid_path, |
|
9 |
+ :command => "pwd", |
|
10 |
+ :expected_update_period_in_days => "1", |
|
11 |
+ } |
|
12 | 12 |
|
13 |
- @checker = Agents::CommandAgent.new(:name => "somename", :options => @valid_params) |
|
14 |
- @checker.user = users(:jane) |
|
15 |
- @checker.save! |
|
13 |
+ @checker = Agents::CommandAgent.new(:name => "somename", :options => @valid_params) |
|
14 |
+ @checker.user = users(:jane) |
|
15 |
+ @checker.save! |
|
16 | 16 |
|
17 |
- @event = Event.new |
|
18 |
- @event.agent = agents(:jane_weather_agent) |
|
19 |
- @event.payload = { |
|
20 |
- :command => "pwd" |
|
21 |
- } |
|
22 |
- @event.save! |
|
23 |
- end |
|
17 |
+ @event = Event.new |
|
18 |
+ @event.agent = agents(:jane_weather_agent) |
|
19 |
+ @event.payload = { |
|
20 |
+ :command => "pwd" |
|
21 |
+ } |
|
22 |
+ @event.save! |
|
23 |
+ end |
|
24 | 24 |
|
25 |
- describe "validation" do |
|
26 |
- before do |
|
27 |
- @checker.should be_valid |
|
28 |
- end |
|
25 |
+ describe "validation" do |
|
26 |
+ before do |
|
27 |
+ @checker.should be_valid |
|
28 |
+ end |
|
29 | 29 |
|
30 |
- it "should validate presence of necessary fields" do |
|
31 |
- @checker.options[:command] = nil |
|
32 |
- @checker.should_not be_valid |
|
33 |
- end |
|
30 |
+ it "should validate presence of necessary fields" do |
|
31 |
+ @checker.options[:command] = nil |
|
32 |
+ @checker.should_not be_valid |
|
33 |
+ end |
|
34 | 34 |
|
35 |
- it "should validate path" do |
|
36 |
- @checker.options[:path] = 'notarealpath/itreallyisnt' |
|
37 |
- @checker.should_not be_valid |
|
38 |
- end |
|
39 |
- end |
|
35 |
+ it "should validate path" do |
|
36 |
+ @checker.options[:path] = 'notarealpath/itreallyisnt' |
|
37 |
+ @checker.should_not be_valid |
|
38 |
+ end |
|
39 |
+ end |
|
40 | 40 |
|
41 |
- describe "#working?" do |
|
42 |
- it "checks if its generating events as scheduled" do |
|
43 |
- @checker.should_not be_working |
|
44 |
- @checker.check |
|
45 |
- @checker.reload.should be_working |
|
46 |
- three_days_from_now = 3.days.from_now |
|
47 |
- stub(Time).now { three_days_from_now } |
|
48 |
- @checker.should_not be_working |
|
49 |
- end |
|
50 |
- end |
|
41 |
+ describe "#working?" do |
|
42 |
+ it "checks if its generating events as scheduled" do |
|
43 |
+ @checker.should_not be_working |
|
44 |
+ @checker.check |
|
45 |
+ @checker.reload.should be_working |
|
46 |
+ three_days_from_now = 3.days.from_now |
|
47 |
+ stub(Time).now { three_days_from_now } |
|
48 |
+ @checker.should_not be_working |
|
49 |
+ end |
|
50 |
+ end |
|
51 | 51 |
|
52 |
- describe "#check" do |
|
53 |
- it "should check that initial run creates an event" do |
|
54 |
- expect { @checker.check }.to change { Event.count }.by(1) |
|
55 |
- end |
|
56 |
- end |
|
52 |
+ describe "#check" do |
|
53 |
+ it "should check that initial run creates an event" do |
|
54 |
+ expect { @checker.check }.to change { Event.count }.by(1) |
|
55 |
+ end |
|
56 |
+ end |
|
57 | 57 |
|
58 |
- describe "#receive" do |
|
59 |
- it "checks if creates events" do |
|
60 |
- @checker.receive([@event]) |
|
61 |
- Event.last.payload[:path].should == @valid_path |
|
62 |
- end |
|
63 |
- it "checks if options are taken from event" do |
|
64 |
- @event.payload[:command] = 'notarealcommand' |
|
65 |
- @checker.receive([@event]) |
|
66 |
- Event.last.payload[:command].should == 'notarealcommand' |
|
67 |
- end |
|
68 |
- end |
|
58 |
+ describe "#receive" do |
|
59 |
+ it "checks if creates events" do |
|
60 |
+ @checker.receive([@event]) |
|
61 |
+ Event.last.payload[:path].should == @valid_path |
|
62 |
+ end |
|
63 |
+ it "checks if options are taken from event" do |
|
64 |
+ @event.payload[:command] = 'notarealcommand' |
|
65 |
+ @checker.receive([@event]) |
|
66 |
+ Event.last.payload[:command].should == 'notarealcommand' |
|
67 |
+ end |
|
68 |
+ end |
|
69 | 69 |
|
70 | 70 |
end |